// use std::io::Write;
// use std::io::Read;
use std::fs;
use std::process::Command;
use std::process::Stdio;
use std::error::Error;
use std::str;
use std::path::Path;
use std::sync::Mutex;
use std::collections::VecDeque;
use std::collections::HashMap;

use encoding::all::GBK;
use encoding::{DecoderTrap, Encoding};
use threadpool::ThreadPool;
use colored::Colorize;

use crate::ChainData;
use crate::BRD_TP_CMD;
use crate::Chain;
use crate::STATUS_RUN;
use crate::STATUS_SUSS;
use crate::STATUS_ERR;
use crate::OUTPUT_TO_LOG;

// use crate::output::Wraper;
// use crate::output::WraperParser;

pub type OutErr<T> = Result<T, Box<dyn Error>>;
pub const OUTPUT_HEAD:&str = "[OUT]";

lazy_static!{
    static ref RUNNING_POOL:Mutex<ThreadPool> = {
        Mutex::new(ThreadPool::new(36))
    };

    static ref WAIT_TASK:Mutex<VecDeque<String>> = {
        Mutex::new(VecDeque::new())
    }; 

    static ref RUNNING_TASK:Mutex<HashMap<String,String>> = {
        Mutex::new(HashMap::new())
    };
}


fn std_redirect(dst: Option<String>) -> Option<(fs::File, fs::File)> {
    let _stdout: fs::File;
    let _stderr: fs::File;
    if let Some(out_file) = dst {
        let p = Path::new(&out_file);
        if p.parent().unwrap().exists() && p.exists() {
            _stdout = fs::OpenOptions::new().append(true).open(p).expect("create this file error");
            // _stdout = fs::File::create(p).expect("create this file error");
            _stderr = _stdout.try_clone().expect("copy stderr error ");
            return Some((_stdout, _stderr));
        }else{
            _stdout = fs::File::create(p).expect("create this file error");
            _stderr = _stdout.try_clone().expect("copy stderr error ");
            return Some((_stdout, _stderr));
        }
    }
    None
}

fn check_cmd(cmd:&str) -> Option<String>{
    let runner:Vec<String> = cmd.trim().split(' ').map(|x| x.to_string()).collect();
    let r = runner.first().unwrap();
    if !cfg!(target_os = "windows"){
        for dir in ["/usr/bin/", "/bin/", "/sbin/", "/usr/sbin/", "/usr/local/bin"].iter(){
            let p = Path::new(&dir).join(&r);
            if p.exists() {
                return Some(cmd.to_string().replace(r.as_str(), p.to_str().unwrap()));
            }
        }
        None
    }else{
        Some(cmd.to_string())
    }
}

pub fn shell(cmd_string: &str,output_file: Option<String>) -> OutErr<(i32,String)> {
    let mut buf = String::new();
    let mut status = 0;
    if let Some(cmd) = check_cmd(cmd_string){
        
        if let Some((stdout, stderr)) = std_redirect(output_file) {
            let out =  if cfg!(target_os = "windows") {
                Command::new("cmd")
                    .arg("/C")
                    .arg(format!("{}", cmd))
                    .stdout(Stdio::from(stdout))
                    .stderr(Stdio::from(stderr))
                    .spawn()
                    .expect("failed to execute process")
                    .wait_with_output()
                    .expect("failed wait output")
            } else {
                Command::new("sh")
                    .arg("-c")
                    .arg(format!("{}", cmd))
                    .stdout(Stdio::from(stdout))
                    .stderr(Stdio::from(stderr))
                    .spawn()
                    .expect("failed to execute process")
                    .wait_with_output()
                    .expect("failed wait output")
            };
            // log::info!("output {}", std::str::from_utf8(&out.stdout).unwrap().yellow());
            status = out.status.code().expect("get shell code error!");
        }else{
            let output = if cfg!(target_os = "windows") {
                Command::new("cmd")
                    .arg("/C")
                    .arg(format!("{}", cmd))
                    .output()
                    .expect("failed to execute process")
            } else {
                Command::new("sh")
                    .arg("-c")
                    .arg(format!("{}", cmd))
                    .output()
                    .expect("failed to execute process")
            };
            if !output.status.success() {
                buf.push_str(
                    match str::from_utf8(&output.stderr) {
                        Ok(e) => e.to_string(),
                        _ => {
                            GBK.decode(&output.stderr, DecoderTrap::Ignore).unwrap()
                        }
                    }.as_str()
                );
                // log::info!("output {}", buf.yellow());
                status = output.status.code().expect("get shell code error");
            }
            buf.push_str(match str::from_utf8(&output.stdout) {
                Ok(e) => e.to_string(),
                _ => GBK.decode(&output.stdout, DecoderTrap::Ignore).unwrap()
                }.as_str()
            );
            
            // status = out.status.code().expect("get shell code error!");
        }
    }else{
        log::error!("runner not exists : {}", &cmd_string);
    }
    Ok((status, buf))
}


pub trait CmdRun {
    fn run(&mut self) -> i32;
    fn run_pass(&mut self, client:&crate::Client) -> i32;
    fn ps() -> HashMap<String, String>;
    fn wait();
}

impl CmdRun for Chain{
    fn wait(){
        let running_task = RUNNING_POOL.lock().expect("lock runing pool");
        running_task.join();
    }
    fn ps() -> HashMap<String, String>{
        let running_task = RUNNING_TASK.lock().expect("lock runing pool");
        running_task.clone()
    }
    fn run_pass(&mut self, _:&crate::Client) -> i32{
        let mut running_task = RUNNING_TASK.lock().expect("lock runing pool");
        if self.task.main.len() == 0  || self.task.tp != BRD_TP_CMD{
            return 0;
        }

        if running_task.len() < 32 {
            running_task.insert(self.loc.uid.clone(), self.task.main.clone());
            let output = self.task_path();
            let id = self.loc.uid.clone();
            let command = self.task.get_command();
            let pool = RUNNING_POOL.lock().expect("lock thread pool");
            pool.execute(move || {
                let id = id.clone();
                let command = command.clone();
                if let Ok((code, stdinfo)) = shell(&command, Some(output)){
                    log::info!("finish code : {}", code);
                    if let Some(mut brd) = id.chain_load(){
                        if code == 0{
                            brd.try_wraper();
                            
                            brd.task.status = STATUS_SUSS;
                        }else if code >0{
                            let output = brd.task_path();
                            if let Some(o) = output.file_open_str(){
                                log::error!("error [{}]: \n{}",code,o.bold().yellow());
                            }else{
                                log::error!("error and cat not read output as str [{}]",code);
                            }
                            brd.task.status = STATUS_ERR;
                        }

                        if brd.task.output_tp >= OUTPUT_TO_LOG {

                            // log::info!("try log : {}", code);
                            log::info!("end to log | {}", id.underline().green());
                            let output = brd.task_path();
                            if let Some(b64_str) = output.file_open_str(){
                                if b64_str.len() > 400{
                                    brd.log(&format!("{}{}...",OUTPUT_HEAD,&b64_str[..400]));
                                }else{
                                    brd.log(&format!("{}{}",OUTPUT_HEAD, b64_str));
                                }
                                
                            }
                        }else{
                            log::info!("not log : {}/{}", brd.task.output_tp , OUTPUT_TO_LOG);
                        }
                        if stdinfo.len() > 0{
                            brd.log(&stdinfo);
                        }
                        brd.save();
                    }else{
                        log::error!("load brd failed {}", id.red());
                    }
                }else{
                    log::info!("finish but not code found ");
                };

                {   
                    let mut running_task = RUNNING_TASK.lock().expect("lock when finish task.");
                    running_task.remove(&id);
                };
                {
                    let mut wait_task = WAIT_TASK.lock().expect("lock when finish old task ");
                    if wait_task.len() > 0 {
                        let wait_task_uid = wait_task.pop_front().unwrap();
                        if let Some(mut brd) = wait_task_uid.chain_load(){
                            log::info!("running a new task from wait : {}", brd);
                            brd.run();
                        }else{
                            log::error!("no such brd record in local : {}", wait_task_uid);
                        }
                    }
                }
            });
            self.task.status = STATUS_RUN;
            self.save();
        }else{
            let mut wait_task = WAIT_TASK.lock().expect("lock wait task failed");
            wait_task.push_back(self.loc.uid.clone());
            log::info!("wait task : {}", self.loc.uid.bold().green());
        }
        -1
    }
    fn run(&mut self) -> i32 {
        let mut running_task = RUNNING_TASK.lock().expect("lock runing pool");
        if self.task.main.len() == 0  || self.task.tp != BRD_TP_CMD{
            return 0;
        }

        if running_task.len() < 32 {
            running_task.insert(self.loc.uid.clone(), self.task.main.clone());
            let output = self.task_path();
            let id = self.loc.uid.clone();
            let command = self.task.get_command();
            let pool = RUNNING_POOL.lock().expect("lock thread pool");
            pool.execute(move || {
                let id = id.clone();
                let command = command.clone();
                if let Ok((code, stdinfo)) = shell(&command, Some(output)){
                    log::info!("finish code : {}", code);
                    if let Some(mut chain) = id.chain_load(){
                        if code == 0{
                            chain.try_wraper();
                            chain.task.status = STATUS_SUSS;
                        }else if code >0{
                            let output = chain.task_path();
                            if let Some(o) = output.file_open_str(){
                                log::error!("error [{}]: \n{}",code,o.bold().yellow());
                            }else{
                                log::error!("error and cat not read output as str [{}]",code);
                            }
                            chain.task.status = STATUS_ERR;
                        }

                        if chain.task.output_tp >= OUTPUT_TO_LOG {

                            // log::info!("try log : {}", code);
                            log::info!("end to log | {}", id.underline().green());
                            let output = chain.task_path();
                            if let Some(b64_str) = output.file_open_str(){
                                if b64_str.len() > 400{
                                    chain.log(&format!("{}{}...",OUTPUT_HEAD,&b64_str[..400]));
                                }else{
                                    chain.log(&format!("{}{}",OUTPUT_HEAD, b64_str));
                                }
                                
                            }
                        }else{
                            log::info!("not log : {}/{}", chain.task.output_tp , OUTPUT_TO_LOG);
                        }
                        if stdinfo.len() > 0{
                            chain.log(&stdinfo);
                        }
                        chain.save();
                    }else{
                        log::error!("load chain failed {}", id.red());
                    }
                }else{
                    log::info!("finish but not code found ");
                };

                {   
                    let mut running_task = RUNNING_TASK.lock().expect("lock when finish task.");
                    running_task.remove(&id);
                };
                {
                    let mut wait_task = WAIT_TASK.lock().expect("lock when finish old task ");
                    if wait_task.len() > 0 {
                        let wait_task_uid = wait_task.pop_front().unwrap();
                        if let Some(mut chain) = wait_task_uid.chain_load(){
                            log::info!("running a new task from wait : {}", chain);
                            chain.run();
                        }else{
                            log::error!("no such chain record in local : {}", wait_task_uid);
                        }
                    }
                }
            });
            self.task.status = STATUS_RUN;
            self.save();
        }else{
            let mut wait_task = WAIT_TASK.lock().expect("lock wait task failed");
            wait_task.push_back(self.loc.uid.clone());
            log::info!("wait task : {}", self.loc.uid.bold().green());
        }
        running_task.len() as i32
    }
}

